# Copyright (c) 2022 Presto Labs Pte. Ltd.
# Author: junglekim

from cc.appcoin2.strategy.umm.support.util import *

import pandas as pd

USE_CONFIG_SERVICE = False
LOG_PRIVATE_MESSAGE = False
STORE_PARSED_MESSAGE = False


if __name__ == "__main__":
  try:
    ctrl_flag = int(sys.argv[3])
  except Exception:
    ctrl_flag = 0

  ctrl_flag = '{0:08b}'.format(ctrl_flag)
  ctrl_flag = ctrl_flag[::-1]

  if ctrl_flag[0] == '1':
    LOG_PRIVATE_MESSAGE = True

  if ctrl_flag[1] == '1':
    USE_CONFIG_SERVICE = True

class StratConfigVmm(StratConfig):
  def __init__(
      self,
      *,
      exchange=None,
      tag=None,
      is_sim=False,
      config_path=None,
      interval=None,
      sim_dir=None,
      is_approach=False,
      initial_pos=None,
      analyzer_feed_machine=None,
      include_tag_info=None):
    self.is_sim = is_sim
    self.sim_dir = sim_dir
    self.interval = interval
    self.is_approach = is_approach
    self.initial_pos = initial_pos
    self.analyzer_feed_machine = analyzer_feed_machine
    self.include_tag_info = include_tag_info

    # converter
    self.app_config = {
        'feed': {'exchanges': {}},
        'order': {'exchanges': {}},
        'log': None,
        'strategy': None,
    }

    if self.is_sim:
      config_path = pathlib.Path(config_path)
    else:
      config_path = get_git_root().joinpath(
          'python', 'coin_deploy', 'vmm', 'config',
          exchange, f'vmm_config_{tag}.json')

    self.config = json.loads(config_path.read_text())
    self.config_path = config_path.as_posix()

  def init_feed(self, mea):
    if 'common' not in self.feed:
      config = {}
      if self.is_sim:
        config["type"] = "FEED_SYSTEM_TYPE_ARCHIVE"
        arch_config = {
            'archive_type': "FASTFEED",
            'machine': self.analyzer_feed_machine,
            'interval': {
                'start': self.interval[0],
                'end': self.interval[1],
            },
            'worker': '0',
        }
        config['archive'] = arch_config
      else:
        config["type"] = "FEED_SYSTEM_TYPE_LIVE"
        config["websocket"] = {"num_workers": 1}
      self.feed['common'] = config

    if mea in self.feed["exchanges"]:
      return

    config = {}
    config["mea"] = mea
    config["products"] = {"norms": []}
    config["recipe"] = self.get_recipe(mea)
    if 'Uniswap' in mea:
      config['archive'] = {}
      config['archive']['machine'] = 'feed-01.ap-northeast-1.aws.huobi'
    self.feed["exchanges"][mea] = config

  def get_recipe(self, mea_str):
    if mea_str == 'Spot.Lmaxdigital.v1':
      return 'realtime'

    config = toml.load(open('data/coin2/feed/subscribers.toml'))
    mea_str = mea_str.replace('.', '_').lower()

    # handle okex v3 and v5 feed
    if 'okex' in mea_str and 'v5' in mea_str:
      mea_str = mea_str.replace('v5', 'v3')

    if 'kucoin' in mea_str:
      return 'realtime'

    config = config[mea_str]
    return config['default_recipe']

  def add_feed_product(self, uniq_product):
    mea = self.get_mea(uniq_product)
    # if 'okex' in mea.lower() and 'v5' in mea.lower():
    #   mea = mea.replace('v5', 'v3')
    self.init_feed(mea)
    norm_prod = self.feed["exchanges"][mea]["products"]["norms"]
    norm_prod.append(get_norm_product(uniq_product))

  def add_order_product(self, uniq_product):
    mea = self.get_mea(uniq_product)
    self.init_order(mea)
    exch = mea.split('.')[1]
    norm_prod = self.order["exchanges"][exch]["products"]["norms"]
    nprod = get_norm_product(uniq_product)
    norm_prod.append(nprod)

    if self.is_sim:
      account = self.order['exchanges'][exch]['sim_config2']['accounts']
      account.append(self.gen_sim_config_account(uniq_product, self.is_approach, self.initial_pos))

  def convert_to_app_config(self):
    feed_product_lst = []
    for k, v in self.config['pricer_config']['configs'].items():
      feed_product_lst.append(k)
      if 'ref_price' in v.keys():
        for ref_info in v['ref_price']:
          base_symbol = ref_info.get('base_ref', None)
          if base_symbol:
            feed_product_lst.append(base_symbol)
          if isinstance(ref_info.get('quote_refs'), dict):
            for quote_symbol, _ in ref_info['quote_refs'].items():
              feed_product_lst.append(quote_symbol)
    for symbol in set(feed_product_lst):
      self.add_feed_product(symbol)
    
    
    for symbol in self.config['executors'].keys():
      self.add_order_product(symbol)
  
  def get_mea(self, uniq_product):
    market, exchange, product = uniq_product.split(':', 2)
    me = f'{market}.{exchange}'
    
    if me == 'Spot.Binance':
      return me + '.v1'

    if me == 'Futures.Binance':
      if 'USDT' in product:
        return me + '.v1'
      else:
        return me + '.v1-delivery'

    if me == 'Spot.Ftx':
      return me + '.v1'

    if me == 'Futures.Ftx':
      return me + '.v1'

    if me == "Spot.Huobi":
      return me + '.v1'

    if me == "Spot.Gateio":
      return me + '.v4'

    if me == "Spot.Mexc":
      return me + '.v3'

    if me == "Spot.Okex":
      return me + '.v5'

    if me == "Spot.Kucoin":
      return me + '.v1'

    if me == "Spot.Coinone":
      return me + '.v2'

    if me == "Spot.Mercado":
      return me + '.v4'

    if me == "Spot.Bitmart":
      return me + '.v3'
    if me == "Spot.Bithumb":
      return me + '.v2'
    
    if me == "Spot.Bitbank":
      return me + ".v1"

    if me == "Spot.Uniswap":
      pi_v2_dir = os.path.expandvars("${COIN_REPO}/data/coin2/product_info/Spot.Uniswap.v2.json")
      pi_v3_dir = os.path.expandvars("${COIN_REPO}/data/coin2/product_info/Spot.Uniswap.v3.json")
      with open(pi_v3_dir) as f:
        pi_v3 = pd.DataFrame(json.load(f)['product_infos'])
      with open(pi_v2_dir) as f:
        pi_v2 = pd.DataFrame(json.load(f)['product_infos'])
      if product in pi_v3['symbol'].tolist():  # use Uniswap V3 if available
        return me + '.v3'
      elif product in pi_v2['symbol'].tolist():
        return me + '.v2'
    if me == "Spot.Phemex":
      return me + '.v1'
    # If not found, raise exception.
    raise(Exception('Unknown product: ' + uniq_product))

  def get_pos_range(self, uniq_product):
    conf_exe = self.config['executors'][uniq_product]
    return conf_exe['min_pos'], conf_exe['max_pos']

  def is_valid_key(self, sub, key):
    valid_keys = {
      'executor':{
        'reserve',
        'sticky_bp',
        'lot_size',
        'max_pos',
        'min_pos',
      },
      'pricer':{
        'window_size_minute',
        'basket_size_minute',
        'default_edge_bp',
        'max_edge_bp',
        'dodge_edge_bp',
        'min_value',
        'decay_min',
        'market_window_sec',
        'midp_interval_ms'
      },
      'oc_config': {
        'agg_arb_thold_bp',
        'pass_arb_thold_bp',
        'agg_min_quote',
        'pass_min_quote',
        'agg_order_frequency_sec',
        'pass_order_frequency_sec',
        'arb_full_book_num'
      },
      'configs': {
        'bandwidth_bp',
        'num',
        'randomise',
        'strike_price',
        'risk_free_rate',
        'volatility',
        'expiry',
        'call_num',
        'initial_pos',
        'move_bp',
        'logspace_start',
        'logspace_stop',
        'logspace_num',
        'grid_period_hour',
        'pos_diff_allowance_percentage',
        'calc_real_delta_hour',
        'min_spread_bp',
        'move_sec',
        'qty_tolerance_bp',
        'pos_offset',
        'min_twap_pricing_weight',
        'cancel2order_ratio',
        'is_randomize_post'
      },
      'deco': {
        'start_bp',
        'end_bp',
        'layer_num',
        'value',
        'window_sec',
        'tick_intv_sec',
        'init_volatility',
        'volatility_thold',
        'initial_deco_fill_day_usage_ratio',
        'deco_twap_decay'
      }
    }
    return key in valid_keys[sub]

  def gen_sim_config_account(self, uniq_product, is_approach, initial_pos):
    initial_position = 0
    if initial_pos:
      initial_position = initial_pos

    if not is_approach :
      min_pos, max_pos = self.get_pos_range(uniq_product) 
      reserve_pos = (max_pos + min_pos) / 2.
    else :
      reserve_pos = self.config['executors'][uniq_product]['reserve']

    nprod = get_norm_product(uniq_product)
    start_time = to_timestamp(self.interval[0])
    end_time = to_timestamp(self.interval[1])
    sim_res_path = os.path.join(self.sim_dir, '.'.join(self.interval + [uniq_product, "pb"]))
    config = {
        "sim_account_id": 0,
        "sim_result_filepath": sim_res_path,
        "relative_norm": nprod,
        "initial_position": initial_position,
        "reserve_position": reserve_pos,
        "is_snapshot": False,
        "latency_sec": 0.1,
        "cancel_latency_sec": 0,
        "dump_fill_only": False,
        "result_format": "RESULT_FORMAT_PROTO_FILE_DUMP",
        "start_timestamp": start_time,
        "end_timestamp": end_time,
        "use_order_latency_model": False,
        "makerfill_tolerance_bps": 0.0,
        "use_exchange_timestamp": True
    }
    if self.include_tag_info:
      config['write_tag'] = True
    return config

  def init_order(self, mea):
    exch = mea.split('.')[1]
    if exch in self.order["exchanges"]:
      return

    path = 'needed'
    config_dir_path = pathlib.Path.home().joinpath('config')
    key_path = config_dir_path.joinpath(path).as_posix()

    config = {}
    config["mea"] = mea

    if self.is_sim:
      config["type"] = "ORDER_SYSTEM_TYPE_SIM_OG"
    else:
      config["type"] = "ORDER_SYSTEM_TYPE_LIVE_OG"

    config["products"] = {"norms": []}
    config["connection_config"] = {
        "key_filepath": key_path,
    }
    config["order_logger_config"] = {"store_parsed_message": STORE_PARSED_MESSAGE}
    # config["order_gateway_config"] = {"log_private_exchange_message": LOG_PRIVATE_MESSAGE}

    if os.getenv('PREX_SANDBOX'):
      config['connection_config'].update({
          "rest_host": "api.sandbox.prex.sh",
          "ws_host": "api.sandbox.prex.sh",
      })

    if os.getenv('PREX_DEV'):
      config['connection_config'].update({
          "rest_host": "api.dev.pc.prex.sh",
          "ws_host": "api.dev.pc.prex.sh",
      })

    if self.is_sim:
      config['sim_config2'] = {'accounts': []}

    self.order["exchanges"][exch] = config

  def update_params(self, params):
    for key, value in params.items():
      if value is None:
        continue

      if self.is_valid_key('executor', key):
        for config in self.config['executors'].values():
          config[key] = value

      elif self.is_valid_key('pricer', key):
        for config in self.config['pricer_config']['configs'].values():
          config[key] = value

      elif self.is_valid_key('oc_config', key):
        self.config['oc_config'][key] = value
          # config[key] = value

      elif self.is_valid_key('configs', key):
        for config in self.config['oc_config']['configs'].values():
          config[key] = value

      elif self.is_valid_key('deco', key):
        for config in self.config['oc_config']['configs'].values():
          if key in {'window_sec', 'tick_intv_sec', 'init_volatility', 'volatility_thold'}:
            config['decos']['volatility_config'][key] = value
          if key in {'initial_deco_fill_day_usage_ratio', 'deco_twap_decay'}:
            for deco_layer_num, _ in config['decos']['defense']['deco_twap_decay'].items():
              config['decos']['defense']['deco_twap_decay'][deco_layer_num] = value
      else:
        continue

  def gen_config(self):
    self.init_log()
    self.convert_to_app_config()

    dt = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + '_' + random_string(6)
    app_config = pathlib.Path(tempfile.gettempdir()).joinpath(f'vmm_appconfig_{dt}.json')
    app_config.write_text(json.dumps(self.app_config, indent=2))
    self.app_config_path = app_config

    strat_config = pathlib.Path(tempfile.gettempdir()).joinpath(f'vmm_config_{dt}.json')
    strat_config.write_text(json.dumps(self.config, indent=2))
    self.strat_config_path = strat_config

    log_name = 'vmm'
    app_relpath = "bazel-bin/cc/appcoin2/strategy/vmm/vmm_runner"
    self.cmd_line = f"{app_relpath} --driver={app_config} --strat_config={strat_config} " + \
        "--logtostderr " + \
        f"2>> ${{HOME}}/data/log/{log_name}_stderr " + \
        f"1>> ${{HOME}}/data/log/{log_name}_stdout"
    template = """
while true; do
{cmd}
sleep 5
done
"""
    self.run_script = template.format(cmd=self.cmd_line)


def main(argv):
  sconfig = StratConfigVmm(exchange=argv[1], tag=argv[2])
  sconfig.gen_config()
  print(sconfig.run_script)


if __name__ == '__main__':
  sys.exit(main(sys.argv))
